//  $Id: i2c.c 42 2008-10-04 18:40:36Z jcw $
//  $Revision: 42 $
//  $Author: jcw $
//  $Date: 2008-10-04 14:40:36 -0400 (Sat, 04 Oct 2008) $
//  $HeadURL: http://tinymicros.com/svn_public/arm/lpc2148_demo/trunk/i2c/i2c.c $

#include "i2c0_irq.h"
#include "vic.h"

#if (I2C0_USE_DEBUG == pdON)
#include "stdio_uc.h"
#endif

#include "systime.h"

#define I2C0_DEFAULT_TIMEOUT 100




#include "_config_cpu_.h"

typedef enum {
  	ACKPOLL_NO = 0,
  	ACKPOLL_YES
} i2c_ack_poll_e;


//  Does not define slave RX statuses.  Bus errors (I2C_STAT == 0) is remapped
//  to I2CERR_ERROR in i2cStatus() so that we can use a value of 0 to indicate 
//  no error.
typedef enum {
  	I2CERR_NONE = 0,
  	I2CERR_BUSY,
  	I2CERR_EMPTY,
  	I2CERR_TIMEOUT,
  	I2CERR_TIMEOUTWC,
  	I2CERR_TIMEOUTACKPOLL,
  	I2CERR_NOTIMPLEMENTED,
  	I2CERR_OTHER,
  	I2CERR_BUSERROR,
  	I2CERR_BUSERRORx       = 0x00,
  	I2CERR_STARTTX         = 0x08,
  	I2CERR_REPEATEDSTARTTX = 0x10,
  	I2CERR_SLAWTX_ACKRX    = 0x18,
  	I2CERR_SLAWTX_NACKRX   = 0x20,
  	I2CERR_DATTX_ACKRX     = 0x28,
  	I2CERR_DATTX_NACKRX    = 0x30,
  	I2CERR_ARBLOST         = 0x38,
  	I2CERR_SLARTX_ACKRX    = 0x40,
  	I2CERR_SLARTX_NACKRX   = 0x48,
  	I2CERR_DATRX_ACKTX     = 0x50,
  	I2CERR_DATRX_NACKTX    = 0x58,
  	I2CERR_NOINFO          = 0xf8
} i2c_err_e;


#if (USE_STRING_MESSAGE == pdON)
typedef struct i2cErrnoStr_s {
  	int errno;
  	const char *text;
} i2c_errno_str_t;

static i2c_errno_str_t i2c_errno_str [] = {
  	{ I2CERR_NONE,              "I2CERR_NONE" },
  	{ I2CERR_BUSY,              "I2CERR_BUSY" },
  	{ I2CERR_EMPTY,             "I2CERR_EMPTY" },
  	{ I2CERR_TIMEOUT,           "I2CERR_TIMEOUT" },
  	{ I2CERR_TIMEOUTWC,         "I2CERR_TIMEOUTWC" },
  	{ I2CERR_TIMEOUTACKPOLL,    "I2CERR_TIMEOUTACKPOLL" },
  	{ I2CERR_NOTIMPLEMENTED,    "I2CERR_NOTIMPLEMENTED" },
  	{ I2CERR_OTHER,             "I2CERR_OTHER" },
  	{ I2CERR_BUSERROR,          "I2CERR_BUSERROR" },
  	{ I2CERR_BUSERRORx,         "I2CERR_BUSERRORx" },
  	{ I2CERR_STARTTX,           "I2CERR_STARTTX" },
  	{ I2CERR_REPEATEDSTARTTX,   "I2CERR_REPEATEDSTARTTX" },
  	{ I2CERR_SLAWTX_ACKRX,      "I2CERR_SLAWTX_ACKRX" },
  	{ I2CERR_SLAWTX_NACKRX,     "I2CERR_SLAWTX_NACKRX" },
  	{ I2CERR_DATTX_ACKRX,       "I2CERR_DATTX_ACKRX" },
  	{ I2CERR_DATTX_NACKRX,      "I2CERR_DATTX_NACKRX" },
  	{ I2CERR_ARBLOST,           "I2CERR_ARBLOST" },
  	{ I2CERR_SLARTX_ACKRX,      "I2CERR_SLARTX_ACKRX" },
  	{ I2CERR_SLARTX_NACKRX,     "I2CERR_SLARTX_NACKRX" },
  	{ I2CERR_DATRX_ACKTX,       "I2CERR_DATRX_ACKTX" },
  	{ I2CERR_DATRX_NACKTX,      "I2CERR_DATRX_NACKTX" },
  	{ I2CERR_NOINFO,            "I2CERR_NOINFO" },
};
#else
typedef struct i2cErrnoStr_s {
  	int errno;
} i2c_errno_str_t;

static i2c_errno_str_t i2c_errno_str [] = {
  	I2CERR_NONE,
  	I2CERR_BUSY,
  	I2CERR_EMPTY,
  	I2CERR_TIMEOUT,
  	I2CERR_TIMEOUTWC,
  	I2CERR_TIMEOUTACKPOLL,
  	I2CERR_NOTIMPLEMENTED,
  	I2CERR_OTHER,
  	I2CERR_BUSERROR,
  	I2CERR_BUSERRORx,
  	I2CERR_STARTTX,
  	I2CERR_REPEATEDSTARTTX,
  	I2CERR_SLAWTX_ACKRX,
  	I2CERR_SLAWTX_NACKRX,
  	I2CERR_DATTX_ACKRX,
  	I2CERR_DATTX_NACKRX,
  	I2CERR_ARBLOST,
  	I2CERR_SLARTX_ACKRX,
  	I2CERR_SLARTX_NACKRX,
  	I2CERR_DATRX_ACKTX,
  	I2CERR_DATRX_NACKTX,
  	I2CERR_NOINFO,
};

#endif





i2c_err_e i2c0_errno = I2CERR_NONE;

static volatile int i2c0_BusInUse;
static u8 i2c0_Address;
static u8 *i2c0_DataBuffer;
static int i2c0_DataLenWrite;
static int i2c0_DataLenRead;
static unsigned int i2c0_TimeoutInTicks = ms2tick(I2C0_DEFAULT_TIMEOUT); // I2C0_DEFAULT_TIMEOUT / portTICK_RATE_MS;
static int i2c0_DataCnt;
static i2c_ack_poll_e i2c0_AckPoll = ACKPOLL_NO;
static void i2c0_ISR_Handler (void);
static void i2c0_ISR (void) __irq;

static int i2c0_WaitComplete (int ticks);
static int i2c0_Poll (u8 address);
static int i2c0_KickOff (u8 address, u8 *buffer, int bufferLenWrite, int bufferLenRead, i2c_ack_poll_e ackPoll);
static int i2c0_WriteBufferEx (u8 address, u8 *buffer, u32 bufferLength, int milliseconds);
static int i2c0_ReadBufferEx (u8 address, u8 *buffer, u32 bufferLength, int milliseconds);
static int i2c0_WriteReadBufferEx (u8 address, u8 *buffer, u32 putLength, u32 getLength, int milliseconds);

//------------------------------------------------------------------------------------------------------------------
// Descrição: 	Incializa o barramento I2C
// Parametros: 	o valor da velocidade do barramento em Khz. Exemplo: 400 = 400KHz
// Retorna:		retorna pdPASS se iniciliazou sem problemas ou retorna código do erro
int i2c0_Init (u16 bitrate) {
  	PCONP |= PCI2C0;										 // Ligar o SPI no controle de potencia. Tem que ser antes da configuração, pois após ligar o periféricos, seus regs assumem valores padrões
	
	I2C0_PINSEL = (I2C0_PINSEL & ~I2C0_PINMASK) | I2C0_PINS; // Seleciona o pino externo para a função I2C

  	i2c0_BusInUse = pdFALSE;
  	i2c0_AckPoll = ACKPOLL_NO;
  
  	I2C0CONCLR = 0x6c;
  	I2C0CONSET = I2C0_I2EN;
  	
  	i2c0_SetBitrate(bitrate);								 // Ajusta a velocidade do barramento
  	//I2C0SCLL   = 240;
  	//I2C0SCLH   = 240;

	#if (I2C0_USE_DEBUG == pdON)
	plog("I2C0: Init"CMD_TERMINATOR);
	#endif

  	if (!irq_Install(VIC_I2C0, i2c0_ISR, I2C0_PRIO_LEVEL)) {
		#if (I2C0_USE_DEBUG == pdON)
		plog("I2C0: Init error"CMD_TERMINATOR);
		#endif

		return errIRQ_INSTALL;
	}

	#if (I2C0_USE_DEBUG == pdON)
	plog("I2C0: Init OK"CMD_TERMINATOR);
	#endif

	return pdPASS;
}


//------------------------------------------------------------------------------------------------------------------
// Descrição: 	Ajusta a velocidade do barramento
// Parametros: 	o valor da velocidade do barramento em Khz. Exemplo: 400 = 400KHz
// Retorna: 	Nada
//------------------------------------------------------------------------------------------------------------------
void i2c0_SetBitrate(const u16 bitrate) {
	I2C0SCLL = PCLK / (bitrate*1000*2); // Clock nível baixo
	I2C0SCLH = PCLK / (bitrate*1000*2); // Clock nível alto		
}


int i2c0_GetErrno (void) {
  	return i2c0_errno;
}

#if (USE_STRING_MESSAGE == pdON)
const char *i2c0_StrError (int err) {
  	unsigned int i;

  	for (i = 0; i < arrsizeof (i2c_errno_str); i++)
    	if (i2c_errno_str [i].errno == err)
      		return i2c_errno_str [i].text;

  	return NULL;
}
#endif

void i2c0_SetTimeOut (unsigned int timeoutInMilliseconds) {
  	#if defined(FREE_RTOS)
  		if (timeoutInMilliseconds < portTICK_RATE_MS)
    		timeoutInMilliseconds = portTICK_RATE_MS;
    #else
  		if (timeoutInMilliseconds < 1)
    		timeoutInMilliseconds = 1;
    #endif

  	i2c0_TimeoutInTicks = ms2tick(timeoutInMilliseconds);
}

//  DANGER, WILL ROBINSON!  The callers buffer must persist until we're done
int i2c0_WriteBuffer (u8 address, u8 *buffer, u32 bufferLength) {
  	return i2c0_WriteBufferEx (address, buffer, bufferLength, i2c0_TimeoutInTicks);
}

int i2c0_ReadBuffer (u8 address, u8 *buffer, u32 bufferLength) {
  	return i2c0_ReadBufferEx (address, buffer, bufferLength, i2c0_TimeoutInTicks);
}

int i2c0_WriteReadBuffer (u8 address, u8 *buffer, u32 putLength, u32 getLength) {
  	return i2c0_WriteReadBufferEx (address, buffer, putLength, getLength, i2c0_TimeoutInTicks);
}

int i2c0_WriteBufferPoll (u8 address, u8 *buffer, u32 bufferLength) {
  	int r;

  	if (!(r = i2c0_KickOff (address, buffer, bufferLength, 0, ACKPOLL_NO)))
    	if (!(r = i2c0_WaitComplete (i2c0_TimeoutInTicks)))
      		r = i2c0_Poll (address);

  	return r;
}

int i2c0_WriteReadBufferPoll (u8 address, u8 *buffer, u32 putLength, u32 getLength) {
  int r;

  	if (!(r = i2c0_KickOff (address, buffer, putLength, getLength, ACKPOLL_NO)))
	    if (!(r = i2c0_WaitComplete (i2c0_TimeoutInTicks)))
      		r = i2c0_Poll (address);

  	return r;
}

//###################################################################################################################
// SERVIÇO INTERNOS
//###################################################################################################################
//###################################################################################################################
//
//  Waits for the interrupt handler to mark the I2C bus as no longer in use, then
//  return 0 on success or -1 if any error occurred.
//

static int i2c0_WaitComplete (int ticks) {
  	if (i2c0_BusInUse)   {
  		u32 xTicks;
  		for (xTicks = systime_GetTick() + ticks; i2c0_BusInUse; ) {
      		if (systime_GetTick() > xTicks) {
        		I2C0CONCLR = I2C0_I2ENC;
        		i2c0_errno = I2CERR_TIMEOUTWC;
        		return -1;
      		}
    	}
  	}
  	/*
  	if (i2c0_BusInUse)   {
    	clock_t theFuture;

    	for (theFuture = times (NULL) + ticks; i2c0_BusInUse; ) {
      		if (times (NULL) > theFuture) {
        		I2C0CONCLR = I2C0_I2ENC;
        		i2c0_errno = I2CERR_TIMEOUTWC;
        		return -1;
      		}
    	}
  	}
*/
  	return (i2c0_errno == I2CERR_NONE) ? 0 : -1;
}

//  Disables interrupts then starts sending repeated STARTs until the slave device
//  returns ACK.  This indicates the slave has completed any operation in progress.
static int i2c0_Poll (u8 address) {
  	u32 xTicks = systime_GetTick() + 10; //  	clock_t theFuture = times (NULL) + 10;

  	VICIntEnClr = (1<<VIC_I2C0);

  	while (systime_GetTick() < xTicks) {
    	I2C0CONCLR = I2C0_SI;
    	I2C0CONSET = I2C0_STA;

    	while (!(I2C0CONSET & I2C0_SI));

    	I2C0CONCLR = I2C0_STAC | I2C0_SIC;
    	I2C0DAT = address & ~0x01;

    	while (!(I2C0CONSET & I2C0_SI));

    	if ((i2c0_errno = I2C0STAT) == I2CERR_SLAWTX_ACKRX)
      		break;
  	}

  	if (i2c0_errno != I2CERR_SLAWTX_ACKRX)
    	i2c0_errno = I2CERR_TIMEOUTACKPOLL;

  	I2C0CONCLR = I2C0_SIC;
  	I2C0CONSET = I2C0_STO | I2C0_AA;

  	while (I2C0CONSET & I2C0_STO) ;

  	VICIntEnable = (1<<VIC_I2C0);
  	
  	return (i2c0_errno == I2CERR_SLAWTX_ACKRX) ? 0 : -1;
}

//
static int i2c0_KickOff (u8 address, u8 *buffer, int bufferLenWrite, int bufferLenRead, i2c_ack_poll_e ackPoll) {
  	//  Determine if our first operation will be a write or read.  If both, the
  	//  write always occurs first.
  	if (bufferLenWrite)
    	address &= ~0x01;
  	else if (bufferLenRead)
    	address |= 0x01;
  	else
    	address &= ~0x01;

  	//  Wait until last I2C operation has finished.  
  	if (i2c0_BusInUse && i2c0_WaitComplete (i2c0_TimeoutInTicks)) {
    	i2c0_errno = I2CERR_TIMEOUT;
    	return -1;
  	}

  	//  Mark bus as in use, save the address, buffer and length
  	i2c0_BusInUse = pdTRUE;
  	i2c0_Address = address;
  	i2c0_DataBuffer = buffer;
  	i2c0_DataLenWrite = bufferLenWrite;
  	i2c0_DataLenRead = bufferLenRead;
  	i2c0_DataCnt = 0;
  	i2c0_AckPoll = ackPoll;
  
  	//  Clear any outstanding bits, enable the interface, and send a start bit.
  	//  Once the start bit goes out, the interrupt drive state machine begins.

  	I2C0CONCLR = 0x6c;
  	I2C0CONSET = I2C0_I2EN;
  	I2C0CONSET = I2C0_STA;

  	return 0;
}

static int i2c0_WriteBufferEx (u8 address, u8 *buffer, u32 bufferLength, int milliseconds) {
  	int r;
	#if (I2C0_USE_DEBUG == pdON)
	plog("I2C0: WRITE BUFFER"CMD_TERMINATOR);
	#endif

  	if (!(r = i2c0_KickOff (address, buffer, bufferLength, 0, ACKPOLL_NO))) {
		#if (I2C0_USE_DEBUG == pdON)
		plog("I2C0: wait"CMD_TERMINATOR);
		#endif

    	r = i2c0_WaitComplete (ms2tick(milliseconds));
    }

	#if (I2C0_USE_DEBUG == pdON)
	if (r < 0) plog("I2C0: error"CMD_TERMINATOR);
	#endif

  	return r;
}

static int i2c0_ReadBufferEx (u8 address, u8 *buffer, u32 bufferLength, int milliseconds) {
  	int r;

	#if (I2C0_USE_DEBUG == pdON)
	plog("I2C0: READ BUFFER"CMD_TERMINATOR);
	#endif

  	if (!(r = i2c0_KickOff (address, buffer, 0, bufferLength, ACKPOLL_NO)))
    	r = i2c0_WaitComplete (ms2tick(milliseconds));

	#if (I2C0_USE_DEBUG == pdON)
	if (r < 0) plog("I2C0: error"CMD_TERMINATOR);
	#endif

  	return r;
}

static int i2c0_WriteReadBufferEx (u8 address, u8 *buffer, u32 putLength, u32 getLength, int milliseconds) {
  	int r;

  	if (!(r = i2c0_KickOff (address, buffer, putLength, getLength, ACKPOLL_NO)))
    	r = i2c0_WaitComplete (ms2tick(milliseconds));

  	return r;
}

//###################################################################################################################
// SERVIÇO DE INT
//###################################################################################################################
//###################################################################################################################
static void i2c0_ISR_Handler (void) {
  	i2c0_errno = (I2C0STAT & 0xf8);

  	switch (i2c0_errno) {
    	//  Transmit conditions
    	case I2CERR_BUSERRORx : { 				// 0x00
        	I2C0CONSET = I2C0_STO | I2C0_AA;
        	i2c0_AckPoll = ACKPOLL_NO;
        	i2c0_BusInUse = pdFALSE;
      	} break;

    	case I2CERR_STARTTX : 					// 0x08
    	case I2CERR_REPEATEDSTARTTX : { 		// 0x10
        	I2C0DAT = i2c0_Address;
      	} break;

    	case I2CERR_SLAWTX_ACKRX : 				// 0x18
    	case I2CERR_DATTX_ACKRX : {				// 0x28
        	i2c0_AckPoll = ACKPOLL_NO;

        	if (i2c0_DataLenWrite && (i2c0_DataCnt < i2c0_DataLenWrite)) {
          		I2C0DAT = i2c0_DataBuffer [i2c0_DataCnt++];
          		I2C0CONCLR = I2C0_STAC;
        	} else  {
          		if (!i2c0_DataLenRead) {
            		I2C0CONCLR = I2C0_STAC;
            		I2C0CONSET = I2C0_STO;
            		i2c0_errno = I2CERR_NONE;
            		i2c0_BusInUse = pdFALSE;
          		} else {
            		i2c0_Address |= 0x01;
            		i2c0_DataCnt = 0;
            		I2C0CONSET = I2C0_STA;
          		}
        	}
      	} break;

    	case I2CERR_DATRX_NACKTX : {			// 0x58
        	i2c0_DataBuffer [i2c0_DataCnt++] = I2C0DAT;
        	i2c0_errno = I2CERR_NONE;
      	}

    	case I2CERR_SLAWTX_NACKRX : 			// 0x20
    	case I2CERR_SLARTX_NACKRX : { 			// 0x48
        	if (i2c0_AckPoll)
          		break;
      	}

    	case I2CERR_DATTX_NACKRX : { 			// 0x30
        	I2C0CONCLR = I2C0_STAC;
        	I2C0CONSET = I2C0_STO;
        	i2c0_BusInUse = pdFALSE;
      	} break;

    	case I2CERR_ARBLOST : { 				// 0x38
        	I2C0CONSET = I2C0_STA;
      	} break;

	    //  Receive byte conditions (fall through is intentional)
        case I2CERR_DATRX_ACKTX : { 			// 0x50
        	i2c0_DataBuffer [i2c0_DataCnt++] = I2C0DAT;
      	}

    	case I2CERR_SLARTX_ACKRX : { 				// 0x40
        	if (i2c0_DataCnt < i2c0_DataLenRead - 1) {
          		I2C0CONCLR = I2C0_STAC;
          		I2C0CONSET = I2C0_AA;
        	} else
          		I2C0CONCLR = I2C0_STAC | I2C0_AAC;
      	} break;

    	case I2CERR_NOINFO : break;
    	default: {
        	I2C0CONCLR = I2C0_I2ENC;
        	i2c0_AckPoll = ACKPOLL_NO;
        	i2c0_BusInUse = pdFALSE;
      	} break;
  	}

  	
  	I2C0CONCLR = I2C0_SI;
  	VICSoftIntClr = (1<<VIC_I2C0);												// Limpa interrupção de software
  	VICVectAddr = 0;             												// Limpa o registrador de endereço do ISR, para novas interrupções
}


static void i2c0_ISR (void) {
  	ISR_ENTRY;																	// Procedimento para entrada da interrupção
  	i2c0_ISR_Handler ();
  	ISR_EXIT;																	// Procedimento para saida da interrupção
}
